tags:
- Cpp
Part1:C++11 (Abandoned)
const(ant),望文生义就是"常值"的意思。在C++中,我们使用 const
修饰符用来表示修饰后的数值或字符串是不可改动的,如:
const int i = 20;//int const i = 20;一个道理
const char ch = 'a';
const char* s = "hello world";
const int* p = 100;//指针p指向的数字100是常值,但是指针可以变
int* const p = 100;//定义了一个常指针指向数值为100的区域,这里的数值是可以改变的
const int* const p = 100;//定义了一个指向常量100的常指针
const
修饰的变量和常量是不一样的。常量常常会放到.text
段或.rodata
段,但是const
修饰的变量不一定放在.rodata
段。如果变量是局部变量,它的生命周期会随着函数的返回、栈帧的销毁一同消逝。但是如果const
修饰的是全局变量,则一般会将这个全局变量放在.rodata
段中。
我们在下面的例子中进一步学习。我们定义了全局变量和许多局部变量。其中 const_global
会放到.rodata
段中,global
会放在bss/data段中。在函数中,字符串"hello world"是一个常字符串,会被放到.rodata
段中,其余函数内用const
修饰局部变量的生命周期都会随着栈帧的摧毁而消逝。
const int const_global = 0;
int global = 20;
int constFunc(const char** d,const int** e) {
const char* s = "hello world";
const int i = 5;
*d = s;
*e = &i;
return 0;
}
int main() {
int x = 30;
int y = 40;
const char* a;
const int* b;
constFunc(&a, &b);
return 0;
}
auto
是C++11新加入的关键字,用于自动推导变量的类型。它可以让编译器根据初始化表达式自动确定变量的类型,从而简化代码编写和提高代码的可读性。但是要注意,在使用 auto
时要清楚编译器会给 auto
什么类型。我们可以使用 boost库 来判断变量的具体类型。
在使用auto
时,我们要注意以下几点:
auto
只能推断出类型,而引用不是类型,所以auto
无法推断出引用,要引用只能自己加引用符号。代码演示如下:int i = 100;
auto i2 = i;//i2为 int 类型
auto& refI = i;//refI 为 int& 类型
/*
我们看到,只有自己加入引用类型后 refI 才能变成 int&。
*/
auto
关键字在推断引用的类型时,会直接将引用替换为引用指向的对象。引用不是对象,任何使用引用的地方都可以直接替换成引用指向的对象。int i = 100;
int& refI = i;
auto i2 = refI;//相当于 auto i2 = i; i2 类型为 int
auto& i3 = refI;//相当于auto& i3 = i; i3 类型为 int&
auto
关键字在推断带const
关键字的类型时,若没有引用符号,则会忽略const
的修饰。而保留指向对象的const
,典型的就是指针。const int i = 100;
auto i2 = i;//i2 为 int 类型
int j = 100;
const int* const p = &j;//定义了一个指向常量 int 的常指针p
auto p2 = p;//p2的类型为const int*,是指向常量 int 的指针。
static int k = 100;
auto k2 = k;//k2 为 int 类型
请留意:在这行代码中const int* const p = &j;//定义了一个指向常量 int 的常指针p
,j
并没有被设置为常量。p
是一个指向 const int
的常指针,但这并不改变 j
的本质。只是说不能通过 p
来修改 j
的值。
4. 在auto
关键字推断类型时,如果带引用符号则会保留值类型。
const int i = 100;
auto& i2 = i;//i2 为 const int 类型
int j = 100;
const int *const p = &j;//定义了一个指向常量 int 的常指针p
auto& p2 = p;//p2的类型为const int* const&。
static int k = 100;
auto& k2 = k;//k2 为 int& 类型
auto
前面加上const
,就会永远有const
的含义。int i = 100;
const auto i2 = i;//i2 为 const int 类型
全局变量和静态变量都是存放在.data/.bss
段中的,这些变量在编译过程中就已经被赋予地址,在执行时不会再次调用。我们用代码做简单的演示。
int global_1 = 100;//.data
int global;//.bss
void test(){
static int i;//.bss,在程序启动时自动初始化为0
static int i2 = 0;//.data,且这行代码不会再进程运行时执行
++i;
}
int main(){
test();
test();
}
C++的表达式一般有两部分对象组成,如int i = 100;
。在表达式中,左值(lvalue) 就是能够取地址的那部分(有地址属性,表达式结束后依然存在的持久对象),而不能够取地址的我们称之为右值(rvalue)(表达式结束后就不再存在的对象)。在上面的表达式中,变量对象i
就是左值,10
这样的字面量(字符字面量除外)对象就是右值。
左值来源于C语言的说法,即可以放在等号左边的就叫左值,左值也可以放在等号右边。但右值只能放到赋值操作符的右边,这是因为右值没有持久的存储位置(临时对象),所以不能作为赋值操作的目标。
int i = 100;
int& ref = i;//lvalue reference, no mistake
const int i2 = 100;
int& ref2 = i2;//错误:非常量引用不能绑定到常量
const int& ref2 = i2;//正确
ref2 = 200;//如果绑定后就会违反常量的不可变原则
int& ref3 = 100;//错误:非常量引用不能绑定到右值
const
左值引用:可以对常量起别名,可以绑定左值和右值。const int i =100;
const int& ref = i;//正确
const int& ref = 100;//正确
int i = 100;
int&& rref = 200;//正确
int&& rref2 = (i+1);//正确
int&& rref3 = i++;//正确,i++是一个右值表达式,因为它返回 i 的旧值(临时对象)
int&& rref4 = i;//错误,i是右值
int&& rref5 = ++i;//错误,++i是左值,因为它返回的是 i 的引用类型
在上节课中,我们看到,在使用右值引用时不能绑定左值。在C++11中,我们可以通过move
函数来将左值转换为右值引用。
move
函数move
函数可以对一个左值使用,使操作系统不再在意其地址属性,将其完全视作一个右值看待。move
函数后,操作对象就失去了其地址属性。因此,我们有义务保证之后避免使用该变量的地址属性,也就是不再使用该变量,因为再次使用该变量不可避免地会使用到变量的地址属性。在后面移动语义时会体现move函数意义。#include<utility>
int i = 100;
int&& rref = i;//错误,因为 i 是左值,不能绑定到右值引用
int&& rref2 = std::move(i);//正确,std::move 将 i 转换成右值引用
int&& rref3 = srd::move(++i);//正确,++i 是左值,std::move 将 ++i 转换成右值引用
临时对象是程序执行时生成的中间对象,所有的临时对象都是右值对象,因为临时对象产生后很快就会被销毁。常见的临时对象有:
int get20(){
return 20;//返回一个临时对象
}
int&& i = get20();
double d = 3.14;
int i = static_cast<int>(d);//临时对象存储转换后的值,然后赋给 i 。
int a = 5, b = 10;
int result = (a + b) * 2; // 临时对象存储 a + b 的结果
如果一个对象可以使用调用运算符"()",()里面可以放参数。那么这个对象就是可调用对象。可调用对象的分类有:
Lambda表达式:也称为匿名函数,是一种需要一个函数但不想命名它的情况下使用的简便方法。基本语法是:[capture list] (parameter list) -> return_type { function body }
=
)或按引用(&
)捕获。